package com.wiscamp.ninechapters.problems.domain.models.sheets;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.wiscamp.ninechapters.problems.domain.ProblemsErrorCodes;
import com.wiscamp.ninechapters.problems.domain.models.exams.ScoringRule;
import com.wiscamp.ninechapters.problems.domain.models.problems.questions.Question;
import cube.ddd.domain.Entity;
import cube.ddd.exception.ExceptionUtils;
import lombok.Data;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;

/**
 * 答题卡明细
 */
@Data
public class SheetDetail implements Entity {
    /**
     * 明细编号
     */
    private long detailId = 0;


    /**
     * 答题卡编号
     */
    private long sheetId;

    /**
     * 题编号
     */
    private long problemId = 0;

    /**
     * 问题编号
     */
    private long questionId = 0;

    /**
     * 解题方法步骤图片地址
     */
    private String solutionUrl = "";

    /**
     * 问题获得分数
     */
    private double gainScore = 0;

    /**
     * 是否评分
     */
    private boolean scored = false;

    /**
     * 是否批改
     */
    private boolean reviewed = false;

    /**
     * 批改次数
     */
    private int reviewCount = 0;

    /**
     * 做题开始时间
     */
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime questionBeginTime;

    /**
     * 做题结束时间
     */
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime questionEndTime;

    /**
     * 答题秒数
     */
    private int answeringSeconds = 0;

    /**
     * 语料编号
     */
    private long corpusId = 0;

    /**
     * 问题正确状态， 0-答案全错 1-答案全正确 2-答案部分正确
     */
    private int correctStatus = 0;

    private Collection<SheetAnswer> sheetAnswers = new ArrayList<>();

    public SheetAnswer getFirstAnswer() {
        var optionalAnswer = sheetAnswers.stream().findFirst();
        return optionalAnswer.orElse(null);
    }

    public double scoreAnswers(Question question) {
        var sheetAnswers = this.getSheetAnswers();
        double gainScore = 0;
        if (question.isHasMultipleAnswers()) {
            // 多个答案按正确分数百分比计算
            var correctAnswers = question.getCurrentAnswers();
            if (sheetAnswers.size() != correctAnswers.size()) {
                ExceptionUtils.throwServiceException(ProblemsErrorCodes.SHEET_DETAIL_ANSWER_NOT_MATCH, null);
            }

            var correctAnswerQty = 0;
            var skipAnswerQty = 0;
            for (var sheetAnswer : sheetAnswers) {
                var correctAnswer = correctAnswers.stream().filter(answer -> answer.getAnswerIndex() == sheetAnswer.getAnswerIndex())
                        .findFirst().orElse(null);
                if (Objects.isNull(correctAnswer)) {
                    ExceptionUtils.throwServiceException(ProblemsErrorCodes.SHEET_DETAIL_ANSWER_NOT_MATCH, null);
                }
                sheetAnswer.correctAnswer(correctAnswer);
                if (sheetAnswer.isSkipped()) skipAnswerQty ++;
                if (sheetAnswer.isCorrect()) correctAnswerQty ++;
            }
            double correctRate = correctAnswerQty / sheetAnswers.size();
            var allSkipped = skipAnswerQty == sheetAnswers.size();
            var allCorrect = correctAnswerQty == sheetAnswers.size();

            gainScore = gainScore + calculateGainScore(question.getScoringRule(), allSkipped, allCorrect, correctRate, question.getQuestionScore());
            for (var sheetAnswer : sheetAnswers) {
                if (sheetAnswer.isCorrect()) {
                    sheetAnswer.setGainScore(gainScore / correctAnswerQty);
                }
            }

            reviewAndScore(gainScore, correctRate);
        } else {
            var correctAnswer = question.getCurrentAnswer();
            if (sheetAnswers.size() != 1) {
                ExceptionUtils.throwServiceException(ProblemsErrorCodes.SHEET_DETAIL_ANSWER_NOT_MATCH, null);
            }
            var sheetAnswer = sheetAnswers.stream().findFirst().orElse(null);
            sheetAnswer.correctAnswer(correctAnswer);
            double correctRate = 1;
            if (!sheetAnswer.isCorrect()) correctRate = 0;
            gainScore = calculateGainScore(question.getScoringRule(), sheetAnswer.isSkipped(), sheetAnswer.isCorrect(), correctRate, question.getQuestionScore());
            sheetAnswer.setGainScore(gainScore);
            reviewAndScore(gainScore, correctRate);
        }

        return gainScore;
    }

    private void updateCorrectStatus(SheetDetail detail, double correctRate) {
        if (Double.compare(correctRate, 0) == 0) {
            detail.setCorrectStatus(0);
            return;
        }

        if (Double.compare(correctRate, 1) == 0) {
            detail.setCorrectStatus(1);
            return;
        }

        detail.setCorrectStatus(2);
    }

    public double calculateGainScore(ScoringRule scoringRule, boolean allAnswerSkipped, boolean allAnswerCorrect, double answerCorrectRate, double questionScore) {
        // 如果问题有对应的计分规则，则按计分规则评分
        // 否则直接根据正确与否计分
        if (Objects.isNull(scoringRule)) {
            if (allAnswerSkipped) return 0;

            if (allAnswerCorrect)
                return questionScore;
            else
                return questionScore * answerCorrectRate;
        } else {
            if (allAnswerSkipped) {
                return scoringRule.getSkipScore();
            }

            if (allAnswerCorrect) {
                return scoringRule.getCorrectScore();
            } else
                return scoringRule.getIncorrectScore();
        }
    }

    public void reviewAndScore(double gainScore, double correctRate) {
        this.setScored(true);
        this.setGainScore(gainScore);
        this.setReviewed(true);
        this.setReviewCount(this.reviewCount + 1);
        updateCorrectStatus(this, correctRate);
    }
}
